/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package motiveai.components;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import motiveai.annotation.BehaviourExhaustion;
import motiveai.annotation.DurationOfBehaviour;
import motiveai.factors.Environment;

/**
 *
 * @author oxplay
 */
public abstract class BehaviourEntity<T extends Agent> implements Entity {

    private final static Logger logger = Logger.getLogger(BehaviourEntity.class.getName());
    public static Map<String, BehaviourDescription> descriptionMap = new HashMap<String, BehaviourDescription>();
    private BehaviourDescription behaviourDescription;
    private boolean initialized = false;
    private Environment sourceEnvironment;
    private Environment destEnvironment;
    private float performance = 1;
    private long countMemory = 0;

    public BehaviourEntity() {
    }

    /*
     * this method initialize factors from Annotations and return BehaviourDescription
     */
    private BehaviourDescription initBehaviourData(Class behaviourClass) {
        BehaviourDescription newBehaviourDescription = new BehaviourDescription();
        newBehaviourDescription.behaviourName = behaviourClass.getSimpleName();
        newBehaviourDescription.durationTime = 0;
        newBehaviourDescription.behaviourTime = 0;
        Method[] methods = behaviourClass.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals("behave")) {
                DurationOfBehaviour durationOfBehaviour = method.getAnnotation(DurationOfBehaviour.class);
                if (durationOfBehaviour != null) {
                    newBehaviourDescription.durationTime = durationOfBehaviour.duration();
                }
                BehaviourExhaustion behaviourExhaustion = method.getAnnotation(BehaviourExhaustion.class);
                if (behaviourExhaustion != null) {
                    newBehaviourDescription.behaviourTime = behaviourExhaustion.duration();
                }
            }
        }
        return newBehaviourDescription;
    }

    public void initialize(Environment sourceEnvironment) throws CloneNotSupportedException {
        if (!initialized) {
            initialized = true;
            Class behaviourClass = this.getClass();
            String behaviourName = behaviourClass.getSimpleName();
            behaviourDescription = descriptionMap.get(behaviourName);
            if (behaviourDescription == null) {
                behaviourDescription = initBehaviourData(behaviourClass);
                descriptionMap.put(behaviourName, behaviourDescription);
            }
        }
        this.sourceEnvironment = sourceEnvironment.cloneEnvironment();
        this.destEnvironment = sourceEnvironment.cloneEnvironment();
    }

    /*
     * Return Environment in which Behaviour was done.
     */
    public Environment getBehaviourSourceEnvironment() {
        return this.sourceEnvironment;
    }

    /*
     * can be overridden for user purpose.
     * predefined condition, limit agent decisions to one if true.
     * Return true if agent must use this Behaviour in actual Environment
     * In case when 2 behaviours must be used, it choose the first that appear.
     * But there should NOT be such situations.
     */
    public boolean mustBeUsed(T agent, Environment environment) {
        return false;
    }
    
    /*
     * predefined condition, limit agent decisions
     * Return true if agent can use this Behaviour in actual Environment
     * Return false if agent can't use this Behaviour in actual Environment
     */
    public abstract boolean canBeUsed(T agent, Environment environment);

    /*
     * Return float say about performance element.
     */
    public float getPerformance() {
        return this.performance;
    }

    /*
     * Return counted executions with similar Environment
     */
    public long getCountMemory() {
        return countMemory;
    }

    /*
     * Return Environment after Behaviour was done.
     */
    public Environment getBehaviourDestEnvironment() {
        return this.destEnvironment;
    }

    public abstract float behave(T agent, Environment environment);
    
    public void execute(T agent, int maxCountLearn) {
        float additionalPerformance = behave(agent, destEnvironment);
        destEnvironment.updateData();
        if (maxCountLearn > countMemory) {
            countMemory++;
            float gainedPerformance = (this.destEnvironment.getPerformance() - this.sourceEnvironment.getPerformance()) + additionalPerformance;
            this.performance = (this.performance * countMemory + gainedPerformance) / (countMemory + 1);
        }
        agent.setAwakeTime(behaviourDescription.durationTime);
        agent.addBehaviourExhaustion(behaviourDescription);
        
        
    }

}
